---
slug:  react-router-dom-5.3.4 #路径，不能重复
title:  react-router-dom-5.3.4 # 标题
authors: [] # 作者，在authors.yml配置
tags: [facebook, hello, docusaurus] # 标签，在tags.yml配置
toc_min_heading_level: 2 # 最小展示标题
toc_max_heading_level: 5 # 最大展示标题
---
import {dataSource,columns} from "@site/src/components/MdxComp/comp/table-data/reactRouterDom.js";

### 安装
```shell
npm install react-router-dom@5.3.4
```

---

### 使用

1. `HashRouter`
    * 把所有需要渲染的内容包起来，开启`HASH`路由

    * 开启后整个页面地址默认都会设置一个`#/`哈希值

2. `Link`
    * 实现路由切换/渲染的组件
    * 最后渲染完毕的依然是`a`标签
    * 可以根据路由模式，自动设定点击a标签的行为

3. `Route`
    * 根据`Link.to`的路由规则匹配`path`
        * `exact`
            * 必须严格匹配
        * `component` 渲染什么组件
        * `render` 传入函数，函数的返回值就是渲染的内容
    * 如果放在最后一项且`path`是`*`或不写则代表上面的路径都不匹配的话执行这个规则

4. `Switch`
    * 确保路由中,只要有一项匹配就不再往下匹配
5. `Redirect`
    * 遇到`Redirect`组件则页面**立即**跳转
    * 可以不用把不写`path`的`Router`组件放在最后一位的方式，用Redirect实现都不匹配时的重定向
        * `from` 从那个地址来的
        * `to` 重定向到那个地址
        * `exat` 对于`from`是否需要精准匹配

```jsx title="src/App.jsx"
import './App.css';
import {First} from './views/First';
import {Second} from './views/Second';
import {Fourth} from './views/Fourth';
import {HashRouter,Link,Route,Switch,Redirect} from 'react-router-dom';
function App() {
  return (
    <HashRouter>
      <nav>
      <div>
        <Link to={'/'}>first</Link>
      </div>
      <div>
        <Link to={'/second'}>second</Link>
      </div>
      <div>
        <Link to={'/third'}>third</Link>
      </div>
          <div>
              <Link to={'/second/third/fourth'}>fourth</Link>
          </div>
    </nav>
        <div>
            <h1>渲染区域</h1>
            <Switch>
                <Route path="/" exact component={First} />
                <Route path="/second" component={Second} />
                <Route path="/third" component={Third} />
                <Route path="/second/third/fourth" component={Fourth} />
                {/*<Route path={'*'} component={404组件}/>*/}
                <Redirect to={'/'}/>
            </Switch>
        </div>
    </HashRouter>
  );
}

export default App;


```

#### 路由的匹配规则

> react-router-dom中5版本的默认是模糊匹配(非精准匹配)

* 路由地址：`<Link to={''}/>`的地址

* 页面地址: 浏览器地址栏 `/#`后的地址

**比的就是页面地址是否包含路由地址**

<TableComp columns={columns} datasource={dataSource}/>


### 嵌套路由

#### 嵌套路由-一级路由

```jsx title="src/App.jsx"
import React from 'react';
import {Route, Redirect, Link, HashRouter, Switch} from 'react-router-dom';
import {A} from './A';
import {B} from './B';
import {C} from './C';
/**
 * 嵌套路由
 * @returns {Element}
 * @constructor
 */
export const App = function () {
    return (
        <>
            <div>
                <HashRouter>
                    <div>
                        <Link to='/a'>go to a Component</Link>
                    </div>
                    <div>
                        <Link to={'/b'}>go to b Component</Link>
                    </div>
                    <div>
                        <Link to='/c'>go to c Component</Link>
                    </div>

                <div>
                    <Switch>
                        {/*如果是/地址则重定向到a路由*/}
                        <Redirect from={'/'} to='/a' exact={true} />
                        <Route path='/a' component={A} />
                        <Route path='/b' component={B} />
                        <Route path='/c' component={C} />
                        {/*如果路由都不匹配则重定向至/*/}
                        <Redirect to={'/'} />
                    </Switch>
                </div>
                </HashRouter>
            </div>
        </>
    )

};

```


#### 嵌套路由-二级路由

> 二级路由的path不能省


```jsx title="src/A.jsx"
import React from 'react';
import {Route, Link, Switch, Redirect} from 'react-router-dom';
import {AOne} from "./loop-child/AOne";
import {ATwo} from "./loop-child/ATwo";
import {AThree} from "./loop-child/AThree";

export const A = function () {
    return (
        <>
            <div>
                <Link to='/a/a1'>a1</Link>
            </div>
            <div>
                <Link to='/a/a2'>a2</Link>
            </div>
            <div>
                <Link to='/a/a3'>a3</Link>
            </div>

            <div>
                <Switch>
                    {/*默认显示a1，path中的上级路由地址不能省*/}
                    <Redirect exact from={'/a'} to='/a/a1' />
                    <Route path='/a/a1' component={AOne} />
                    <Route path='/a/a2' component={ATwo} />
                    <Route path='/a/a3' component={AThree} />
                </Switch>
            </div>
        </>
    )

};

```

### 路由表

> 不需要复杂的写Route

```js title="src/route/routes.js"
import {RootA} from '../views/routeList/root/RootA';
import {ARoutes} from "./route-children/ARoutes";
import {lazy} from "react";

/**
 * 路由元信息
 * path 匹配地址
 * redirect： true：重定向，false：无redirect
 * from: 从哪里来
 * to：去哪里
 * exact:是否精准匹配
 * meta：路由其余信息
 * children:子路由
 * @type {*[]}
 */
const routes = [
    {
        redirect:true,
        exact: true,
        from:'/',
        to:'/a'
    },
    {
      redirect:false,
      path: '/a',
      component: RootA,
      children: ARoutes
    },
    {
        redirect:false,
        path:'/b',
        component:lazy(()=>import("../views/routeList/root/RootB")),
    }
]

export default routes;

```

#### 整合路由信息生成路由组件

```jsx title="src/router/index.js"

import React,{Suspense} from "react";
import {Redirect,Route,Switch} from 'react-router-dom';
import {AOne} from "../views/loopRoute/loop-child/AOne";

const renderRoute = (props) => {

    const {routes} = props;

    return (
        <Switch>
            {routes.map((item,index)=>{
                const {redirect , exact,from,to,component:Component,path} = item;
                let config = {path};
                if (redirect) {
                    return <Redirect key={index} from={from} to={to} exact={exact}/>;
                }
                if (exact) {
                    config.exact = exact;
                }
                return <Route key={index} {...config}  render={()=>{
                    return <>
                        <Suspense fallback={<>loading...</>}>
                            <Component/>
                        </Suspense>
                    </>
                }}/>
            })}
        </Switch>
    )
}

export default renderRoute;

```




#### `lazy`懒加载

> 直接把所有的组件都写在这一个文件里面的话会导致打包文件过大，因此我们使用`react`的`lazy`函数解决问题

##### 报错问题解决

> 下面这种错误需要的是指定的组件需要默认导出`export default`

```shell
Element type is invalid. Received a promise that resolves to: undefined. Lazy element type must resolve to a class or function.
    at beginWork

```
##### 使用

**RootB需要`export default`**

```js title="src/route/routes.js"
const routes = [
    {
        redirect:false,
        path:'/b',
        component:lazy(()=>import("../views/routeList/root/RootB")),
    }
]
```


##### 指定多个路由组件在一个打包文件中渲染，这样可以避免生成多余的打包文件

> /*webpackChunkName:"RootA"*/ 指定同名即可，也是在指定打包文件的名字

```js title="src/route/routes.js"
import {RootA} from '../views/routeList/root/RootA';
import {ARoutes} from "./route-children/ARoutes";
import {lazy} from "react";

/**
 * 路由元信息
 * path 匹配地址
 * redirect： true：重定向，false：无redirect
 * from: 从哪里来
 * to：去哪里
 * exact:是否精准匹配
 * meta：路由其余信息
 * children:子路由
 * @type {*[]}
 */
const routes = [
    {
        redirect:false,
        path:'/b',
        component:lazy(()=>import(/*webpackChunkName:"RootA"*/"../views/routeList/root/RootB")),
    },
    {
        redirect:false,
        path:'/c',
        component:lazy(()=>import(/*webpackChunkName:"RootC"*/'../views/routeList/root/RootC')),
    }
]

export default routes;

```

##### 在整合生成的路由组件中使用`Suspense`组件实现异步路由

> Suspense的fallback属性可以指定在路由未加载时渲染的内容

```jsx title="src/router/index.js"

import React,{Suspense} from "react";
import {Redirect,Route,Switch} from 'react-router-dom';

const renderRoute = (props) => {

    const {routes} = props;

    return (
        <Switch>
            {routes.map((item,index)=>{
                const {redirect , exact,from,to,component:Component,path} = item;
                let config = {path};
                if (redirect) {
                    return <Redirect key={index} from={from} to={to} exact={exact}/>;
                }
                if (exact) {
                    config.exact = exact;
                }
                return <Route key={index} {...config}  render={()=>{
                    return <>
                        <Suspense fallback={<>loading...</>}>
                            <Component />
                        </Suspense>
                    </>
                }}/>
            })}
        </Switch>
    )
}

export default renderRoute;

```


### 实现js方式手动跳转路由

> `props`方式获取`history`,`location`,`match`需要是`Route`组件渲染


#### react-router-dom的三个hooks函数(必须在`HashRouter`或`BrowserRouter`标签内部)

* useHistory
    * `go` 走指定步
    * `goBack` 往后退一步，等价于`go(-1)`
    * `goForwad` 往前前进一步，等价于`go(1)`
    * `push` 去一个新的地址，并生成一条新的记录
    * `replace` 替换当前地址

* useLocation

* useRouteMatch

> 组件如果是通过`Router.components`渲染的话`props`(render渲染的话获取不到)就自动获取到三个属性,**就不需要上面三个hooks了**

* `history`

* `location`

* `match`

#### 类组件需要通过hoc(高阶组件)获取这三个hooks(v6版本中既没有props，也没有withRouter，只能通过高阶组件)

#### react-router-dom@v5提供了一个高阶组件`withRouter`

```jsx
import React from 'react';
import {withRouter} from 'react-router-dom'
class RouterClazzInnerComp extends React.Component {
    render() {
        console.log(this.props);//会获取到history,location,match
        return (
            <>
                class组件
            </>
        )
    }
}
export default withRouter(RouterClazzInnerComp);
```

## 路由传参

### Link式

```jsx
import React from 'react';
import {Link} from 'react-router-dom';
export const NoRouteComp = function () {
    return (
        <>
            <Link to={{
                pathname: '/',//跳转路径地址
                state: {
                    id:1,
                    name:2,
                    age:3
                },//隐式传参
                search:'id=2'//问号传参
            }} replace>replace 的意思是不新增历史记录而是替换当前历史记录</Link>
        </>
    )
};

```


### 编程式

```jsx
import React from 'react';
import { useHistory} from 'react-router-dom';
export const NoRouteComp = function (props) {
    const history = useHistory();
    return (
        <>
            <button onClick={()=>{
                history.push({
                    pathname: '/',//要去的地址
                    state: {
                        id:1,
                        name:2,
                        age:3
                    },//隐式传参
                    search:'id=2'//浏览器地址问号传参
                });
            }}>back to the root</button>
            {/*这两个是等价的*/}
            <button onClick={()=>{
                history.push({
                    pathname: '/a',//要去的地址
                    state: {},//隐式传参
                    search:''//浏览器地址问号传参
                });
            }}>back to the a</button>

            <button onClick={()=>{
                history.push('/a');
            }}>back to the a</button>
        </>
    )
};
```


### 问号传参

[qs库](docs/前端组件库/qs.mdx)

> 接收到的地址栏是`http://localhost:9320/#/?id=2&name=3&age=3&value=33`

```jsx
import qs from 'qs';
const history = useHistory();
<button onClick={() => {
                history.push({
                    pathname: '/a/a2',
                    state: {},
                    search:qs.stringify({
                        id:2,
                        name:3,
                        age:3,
                        value:'33'
                    })
                })
            }}>
                qs stringify goto root
            </button>
```

#### 接收参数

> 使用location接收参数

> `substring(1)`的目的是把前面的那个?去掉,因为qs处理时候如果带?问号的会把key当作`?key`:`qs.parse('?id=3&name=4');//====> {?id:3,name:4}`


```jsx

import React from 'react';
import {useLocation} from 'react-router-dom';
import qs from 'qs';
 const ChildATwo = function () {
     const location = useLocation();
    return (
        <>
            {
                Object.entries(qs.parse(location.search.substring(1)))
                    .map((value) => {
                    return (
                        <h3>
                            {value[0]}-{value[1]}
                        </h3>
                    )
                })
            }
        </>
    )

};

 export default ChildATwo;
```


### 路由表指定传参

> 对于地址是`/blog/10232233/测试` 这样类似博客的地址很有用

```js title="src/router/routes.js"

const routes = [
    {
        path:'/c/:id/:name',//id和name必传
        name:'c',
        component:lazy(()=>import('c.jsx'))
    },
    {
        path:'/d/:id?/name?',//id和name可传可不传
        name:'d',
        component:lazy(()=>import('d.jsx'))
    }
]
```

> 这样的话push('c')的话无法匹配，因为路由表明确要求需要id和name

```jsx
history.push('/c/100/zs');

```
> 这样传参是匹配的，意思就是id=100,name=zs


#### 接收参数


```jsx

import React from 'react';
import {useRouteMatch,useParams} from "react-router-dom";
const ChildAThree = function () {
    const match = useRouteMatch();
    const params = useParams();
    return (
        <>
            <h3>======================</h3>
            {
                Object.keys(match.params).map(key => {
                    return (
                        <>
                            {key}-{match.params[key]}
                        </>
                    )
                })
            }
            <br/>
            <h3>=========================</h3>
            {
                Object.keys(params).map(key => {
                    return (
                        <>
                            {key}-{params[key]}
                        </>
                    )
                })
            }
        </>
    )

};

 export default ChildAThree;
```


### 隐式传参(更隐蔽,传的值能更多点)

> 在目标组件内刷新的话传递的参数就丢失了

```jsx
import {useHistory} from "react-router";
const history = useHistory();
<button onClick={()=>{
    history.push({
        pathname:'/a/a4',
        state:{
            id:100,
            name:'zs',
            age:30
        },
        search:''
    })
}}> click</button>
```

#### 接收参数

```jsx
import {useLocation} from "react-router";
const location = useLocation();

console.log(location.state);

```

## NavLink
> 和Link一样，只不过会加个active的class，方便设置样式